package com.xiyuan.smartutils.crypto;

import com.xiyuan.smartutils.Asserts;
import com.xiyuan.smartutils.Hexs;
import com.xiyuan.smartutils.constants.CodeConstants;
import com.xiyuan.smartutils.reflect.Unsafe;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;

/**
 * HMAC算法<br><br>
 * 1.注意编码类型支持HmacMD5 | HmacSHA1 | HmacSHA256 | HmacSHA384 | HmacSHA512<br>
 * 2.对结果支持Base64 | HEX | MD5进行编译成字符串<br>
 * 使用时注意和对方协商后
 *
 * @version v1.0.0 @author lgz 2017-8-27 新建与整理
 */
public class HMAC implements CodeConstants {
    static {
        Unsafe.init();
    }
    /**
     * 生成签名数据
     *
     * @param data      待加密的数据
     * @param key       加密使用的key
     * @param algorithm 加密算法 HmacMD5 | HmacSHA1 | HmacSHA256 | HmacSHA384 | HmacSHA512
     * @param retCoding 签名编码 Base64 | HEX | MD5
     * @return 生成签名值
     */
    public static String encrypt(byte[] data, byte[] key, String algorithm, String retCoding) {
        switch (retCoding) {
            case _HEX_:
                return Hexs.toHexString(encrypt(data, key, algorithm));
            case _BASE64_:
                return Base64.encode(encrypt(data, key, algorithm));
            case _MD5_:
                return MD5.encode(encrypt(data, key, algorithm));
            default:
                throw Asserts.exception("不支持的retCoding");
        }
    }
    
    
    /**
     * 生成签名数据
     *
     * @param data      待加密的数据
     * @param key       加密使用的key
     * @param algorithm 加密算法 HmacMD5 | HmacSHA1 | HmacSHA224 | HmacSHA256 | HmacSHA384 | HmacSHA512
     * @return 生成签名值
     */
    public static byte[] encrypt(byte[] data, byte[] key, String algorithm) throws RuntimeException {
        
        try {
            SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            return mac.doFinal(data);
        }
        catch (Exception e) {
            throw e instanceof RuntimeException ? (RuntimeException) e : new RuntimeException(e.getMessage(), e);
        }
    }
    
    
    /**
     * HmacMD5 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] MD5(byte[] src, byte[] key) {
        return encrypt(src, key, "HmacMD5");
    }
    
    /**
     * HmacMD5 生成签名数据
     *
     * @param key 加密使用的key
     * @param src 待加密的数据
     * @return 加密后数据
     */
    public static byte[] MD5(String src, String key) {
        return MD5(src.getBytes(), key.getBytes());
    }
    
    /**
     * HmacMD5 生成签名数据
     *
     * @param key 加密使用的key
     * @param src 待加密的数据
     * @param chs src 数据编码
     * @return 加密后数据
     */
    public static byte[] MD5(String src, String key, Charset chs) {
        byte[] bytes = chs == null ? src.getBytes() : src.getBytes(chs);
        return MD5(bytes, key.getBytes());
    }
    
    
    /**
     * HmacMD5 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeMD5(byte[] src, byte[] key) {
        return HEX.toString(MD5(src, key));
    }
    
    
    /**
     * HmacMD5 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeMD5(String src, String key, Charset chs) {
        return HEX.toString(MD5(src, key, chs));
    }
    
    /**
     * HmacMD5 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeMD5(String src, String key) {
        return HEX.toString(MD5(src, key));
    }
    
    /**
     * HmacSHA1 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA1(byte[] src, byte[] key) {
        return encrypt(src, key, "HmacSHA1");
    }
    
    /**
     * HmacSHA1 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA1(String src, String key) {
        return SHA1(src.getBytes(), key.getBytes());
    }
    
    /**
     * HmacSHA1 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA1(String src, String key, Charset chs) {
        byte[] bytes = chs == null ? src.getBytes() : src.getBytes(chs);
        return SHA1(bytes, key.getBytes());
    }
    
    
    /**
     * HmacSHA1 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA1(byte[] src, byte[] key) {
        return HEX.toString(SHA1(src, key));
    }
    
    /**
     * HmacSHA1 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA1(String src, String key, Charset chs) {
        return HEX.toString(SHA1(src, key, chs));
    }
    
    /**
     * HmacSHA1 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA1(String src, String key) {
        return HEX.toString(SHA1(src, key));
    }
    
    
    /**
     * HmacSHA256 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA256(byte[] src, byte[] key) {
        return encrypt(src, key, "HmacSHA256");
    }
    
    /**
     * HmacSHA256 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA256(String src, String key) {
        return SHA256(src.getBytes(), key.getBytes());
    }
    
    
    /**
     * HmacSHA256 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA256(String src, String key, Charset chs) {
        byte[] bytes = chs == null ? src.getBytes() : src.getBytes(chs);
        return SHA256(bytes, key.getBytes());
    }
    
    /**
     * HmacSHA256 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA256(byte[] src, byte[] key) {
        return HEX.toString(SHA256(src, key));
    }
    
    /**
     * HmacSHA256 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA256(String src, String key) {
        return HEX.toString(SHA256(src, key));
    }
    
    
    /**
     * HmacSHA256 生成签名数据 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA256(String src, String key, Charset chs) {
        return HEX.toString(SHA256(src, key, chs));
    }
    
    
    /**
     * HmacSHA512 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA512(byte[] src, byte[] key) {
        return encrypt(src, key, "HmacSHA512");
    }
    
    
    /**
     * HmacSHA512 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA512(String src, String key) {
        return SHA512(src.getBytes(), key.getBytes());
    }
    
    
    /**
     * HmacSHA512 生成签名数据
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据
     */
    public static byte[] SHA512(String src, String key, Charset chs) {
        byte[] bytes = chs == null ? src.getBytes() : src.getBytes(chs);
        return SHA512(bytes, key.getBytes());
    }
    
    
    /**
     * HmacSHA512 生成签名数据后的 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA512(byte[] src, byte[] key) {
        return HEX.toString(SHA512(src, key));
    }
    
    
    /**
     * HmacSHA512 生成签名数据后的 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA512(String src, String key) {
        return HEX.toString(SHA512(src, key));
    }
    
    /**
     * HmacSHA512 生成签名数据后的 16进制字符串
     *
     * @param src 待加密的数据
     * @param key 加密使用的key
     * @return 加密后数据 16进制字符串
     */
    public static String encodeSHA512(String src, String key, Charset chs) {
        return HEX.toString(SHA512(src, key, chs));
    }
}
